﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utilities;

namespace Geometry
{
   public class MeshTriangle
   {
      ////////////////////////////////////////////////////////////////////////
      //
      protected MeshTriangle()
      {
         scale_ = 0;
      }

      public MeshTriangle leftNeighbour
      {
         get { return leftNeighbour_; }
      }
      public MeshTriangle rightNeighbour
      {
         get { return rightNeighbour_; }
      }
      public MeshTriangle baseNeighbour
      {
         get { return baseNeighbour_; }
      }
      public Point3D points
      {
         get { return points_; }
         set { points_ = value; }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private MeshTriangle inSituSplit()
      {
         MeshTriangle child = null;
         try
         {
            // Now we know it's safe to split into two
            child = create();
            child.scale_ = ++scale_;

            child.baseNeighbour_ = leftNeighbour_;
            if (null != leftNeighbour_)
            {
               leftNeighbour_.baseNeighbour_ = child;
            }
            child.leftNeighbour_ = this;
            child.rightNeighbour_ = null;    // At the edge

            // Turn ourselves into the "right" child
            baseNeighbour_ = rightNeighbour_;
            if (null != rightNeighbour_)
            {
               rightNeighbour_.baseNeighbour_ = this;
            }
            leftNeighbour_ = null;
            rightNeighbour_ = child;

#if DEBUG
            child.name_ = string.Format("l-split({0}, {1})", name_, scale_);
            name_ = string.Format("r-split({0}, {1})", name_, scale_);
#endif
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return child;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public MeshTriangle split()
      {
         try
         {
            // We can only split if our base neighbour does not exist, or
            // if we are its base neighbour too (a diamond)
            if ((null != baseNeighbour_) && (baseNeighbour_.baseNeighbour_ != this))
            {
               // We need to split the base neighbour recursively (ROAM algorithm)
               // This will avoid tearing the mesh when we adjust the heights to
               // get closer to the desired shape
               baseNeighbour_.split();
            }

            // Perform a split on ourselves and the base neighbour (if there is one)
            // See MeshTriangles.jpg for a diagram
            if (null == baseNeighbour_)
            {
               // We are at the edge, so just split ourselves into two
               return inSituSplit();
            }

            // We are part of a diamond, so we need to split both at the same time
            MeshTriangle oldBase = baseNeighbour_;
            MeshTriangle ourChild = inSituSplit();
            MeshTriangle baseChild = oldBase.inSituSplit();

            // Fix up the internal neighbours (note that the currentBase is inverted with
            // respect to us, so the new triangle below ourChild is the old base
            // and the new triangle below us is baseChild
            ourChild.rightNeighbour_ = oldBase;
            oldBase.leftNeighbour_ = ourChild;

            baseChild.rightNeighbour_ = this;
            leftNeighbour_ = baseChild;

            return ourChild;
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return null;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private static void safeDestroy(ref MeshTriangle meshTriangle)
      {
         if (null == meshTriangle)
         {
            return;
         }
         destroy(ref meshTriangle.baseNeighbour_);
         destroy(ref meshTriangle.leftNeighbour_);
         destroy(ref meshTriangle.rightNeighbour_);
         available_.Push(meshTriangle);
         meshTriangle = null;
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public static void destroy(ref MeshTriangle meshTriangle)
      {
         try
         {
            safeDestroy(ref meshTriangle);
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public static MeshTriangle create()
      {
         return (0 == available_.Count) ? new MeshTriangle() : available_.Pop();
      }

      ////////////////////////////////////////////////////////////////////////
      //
      private MeshTriangle addNeighbour(ref MeshTriangle neighbour)
      {
         try
         {
            destroy(ref neighbour);
            neighbour = create();
            neighbour.baseNeighbour_ = this;
#if DEBUG
            if (ReferenceEquals(leftNeighbour_, neighbour))
            {
               neighbour.name_ = string.Format("{0}-left({1})", name_, scale_);
            }
            if (ReferenceEquals(rightNeighbour_, neighbour))
            {
               neighbour.name_ = string.Format("{0}-right({1})", name_, scale_);
            }
            if (ReferenceEquals(baseNeighbour_, neighbour))
            {
               neighbour.name_ = string.Format("{0}-base({1})", name_, scale_);
            }
#endif
         }
         catch (System.Exception ex)
         {
            logger_.exception(ex);
         }
         return neighbour;
      }

      ////////////////////////////////////////////////////////////////////////
      // The number of times we have been split
      public int scale
      {
         get { return scale_; }
      }

      ////////////////////////////////////////////////////////////////////////
      //
      public MeshTriangle addBaseNeighbour() { return addNeighbour(ref baseNeighbour_); }
      public MeshTriangle addLeftNeighbour() { return addNeighbour(ref leftNeighbour_); }
      public MeshTriangle addRightNeighbour() { return addNeighbour(ref rightNeighbour_); }

#if DEBUG
      ////////////////////////////////////////////////////////////////////////
      //
      public string name { get { return name_; } set { name_ = value; } }
      public override string ToString()
      {
         return name_;
      }
#endif

      ////////////////////////////////////////////////////////////////////////
      //
      private MeshTriangle leftNeighbour_;
      private MeshTriangle rightNeighbour_;
      private MeshTriangle baseNeighbour_;
      private int scale_;
      private Point3D points_;

      private static Logger logger_ = new Logger();
      private static Stack<MeshTriangle> mesh_ = new Stack<MeshTriangle>(10000);
      private static Stack<MeshTriangle> available_ = new Stack<MeshTriangle>(10000);
#if DEBUG
      private string name_;
#endif
   }
}
